use std::sync::atomic::AtomicU16;
use std::sync::{Arc, RwLock};

use axum::http::HeaderValue;
use axum::{extract::Path, extract::State, routing::get, Json, Router};
use axum_hello::entity::article_list_view::ArticlesListView;
use axum_hello::layer::log_layer::LogLayer;
use axum_hello::layer::url_layer::UrlLayer;
use axum_hello::scheduler_job::article_read_time_scheduler_job::ArticleReadTimeSchedulerJob;
use sqlx::mysql::MySqlPoolOptions;
use sqlx::MySqlPool;
use tower_http::cors::CorsLayer;

use tklog::info;

pub mod configuration;
pub mod entity;
pub mod job;
pub mod person;

use crate::entity::article::Article;
use crate::entity::article_detail::ArticleDetail;
use crate::entity::article_summary::ArticleSummary;
use crate::entity::article_summary_vec::ArticleSummaryVec;
use crate::person::Person;

use crate::job::file_extractor::Extractor;

#[tokio::main]
async fn main() {
    //初始化日志配置
    configuration::log_config::log_init();

    let workspace = std::env::current_dir().unwrap();
    println!("{:?}", &workspace);
    info!("Current workspace:: {:?}", &workspace.to_string_lossy());
    /*
        let args: Vec<String> = std::env::args().collect();
        if (args).capacity() < 2 {
            //第一个参数是程序名称，第二个参数才是我们输入的参数
            eprintln!("You need to entry the basic_path");
            panic!("Lost the param basic_path");
        }
        let basic_path = Arc::new(args[1].clone());
    */
    let pool = MySqlPoolOptions::new()
        .connect("mysql://user:password@localhost/Blog")
        .await
        .expect("failed to connect database.");

    // default page_size=9
    let default_vec_size = 9 * 2;
    let count_vec = (0..default_vec_size)
        .map(|_| AtomicU16::new(0))
        .collect::<Vec<_>>();
    let mut articles_click_vec: Arc<RwLock<Vec<AtomicU16>>> = Arc::new(RwLock::new(count_vec));
    let UrlLayer = UrlLayer::new(Arc::clone(&articles_click_vec));
    let log_layer = LogLayer {
        target: "hello layer",
    };
    let router = Router::new()
        .route("/", get(articles_view)) //只返回试图,路由作用
        .route("/articles", get(articles_view)) //只返回试图，路由作用
        .route("/articles/:page_num", get(article_list)) //返回具体信息
        .route("/article/:id", get(article_detail))
        .route("/me", get(me))
        .route("/article/new", get(extract_article))
        .layer(
            //设置同源策略
            CorsLayer::new().allow_origin("*".parse::<HeaderValue>().unwrap()),
        )
        .layer(log_layer)
        .layer(UrlLayer)
        .with_state(pool.clone())
        .nest_service("/assets", tower_http::services::ServeDir::new("assets"));

    //开启scheudler
    let _ = ArticleReadTimeSchedulerJob::new(Arc::clone(&articles_click_vec), pool.clone()).start();

    // run our app with hyper, listening globally on port 3000
    let listener = tokio::net::TcpListener::bind("0.0.0.0:80").await.unwrap();
    axum::serve(listener, router).await.unwrap();
}

#[warn(dead_code)]
async fn say_hello(State(pool): State<MySqlPool>) -> String {
    let row: (String,) = sqlx::query_as("SELECT 'hello world, Rust!'")
        .fetch_one(&pool)
        .await
        .expect("failed!");
    //assert_eq!(row.0, 150);
    row.0
}

async fn articles_view() -> ArticlesListView {
    ArticlesListView::new()
}

async fn article_list(
    State(pool): State<MySqlPool>,
    Path(page_num): Path<i32>,
) -> Json<ArticleSummaryVec> {
    info!("page_num: ", page_num);
    let page_size: i32 = 9;
    let offset = (page_num - 1) * page_size;
    info!("the offset is {}", &offset);

    let summary_vec: Vec<ArticleSummary>= sqlx::query_as(r#"SELECT a.id, a.title, a.categoryId, a.summary,path, 
        a.createOn, a.createBy, IFNull(ac.total_times,0) as view_amount
        FROM article a 
        LEFT JOIN access_count ac on ac.article_id=a.id
        WHERE deleted=0 
        order by id desc limit ?, ? "#
        )
	    .bind(offset)
	    .bind(page_size)
		.fetch_all(&pool).await.expect("fetch article list failed!");
    //assert_eq!(row.0, 150);
    let total: i32 = sqlx::query_scalar("select count(id) from article where deleted=0")
        .fetch_one(&pool)
        .await
        .expect("Query page total failed!");
    info!("total: ", &total);
    println!("::::::::::::::::::::::::::{:#?}", &summary_vec);
    let mut vec1: Vec<ArticleSummary> = vec![];
    let mut vec2: Vec<ArticleSummary> = vec![];
    let mut vec3: Vec<ArticleSummary> = vec![];
    for (position, article) in summary_vec.iter().enumerate() {
        info!("position: ", position);
        match position % 3 {
            0 => vec1.push(article.clone()),
            1 => vec2.push(article.clone()),
            2 => vec3.push(article.clone()),
            _ => (),
        }
    }
    Json(ArticleSummaryVec::new(vec1, vec2, vec3, total, page_num))
}

async fn article_detail(State(pool): State<MySqlPool>, Path(id): Path<i32>) -> ArticleDetail {
    info!("the article id is ", &id);

    let article: Article = sqlx::query_as("SELECT id, title, categoryId, summary, path, deleted, createOn, createBy, modifyOn, modifyBy FROM article WHERE ID=?")
            .bind(id)
            .fetch_one(&pool).await.expect("Can not find the article detail");

    let (article_id, article_title, article_create_on) = article.clone().get_info();
    let article_detail = ArticleDetail::new(article_id, article_title, vec![], article_create_on);
    let content = article.read_file().await;
    article_detail.setContent(content)
}

pub async fn extract_article(State(pool): State<MySqlPool>) -> String {
    let current_workspace = std::env::current_dir().unwrap().join("markdown");
    let insert_article_result =
        Extractor::visit_dirs(std::path::Path::new(current_workspace.as_os_str()), &pool).await;
    match insert_article_result {
        Ok(_) => "Insert Article successfully.".to_string(),
        Err(_) => "Insert Article failed!".to_string(),
    }
}

async fn me() -> Person<'static> {
    Person { name: "Owen" }
}

#[cfg(test)]
mod test {
    use super::*;
    use sqlx::mysql::MySqlPoolOptions;
    use std::path::Path;

    #[tokio::test]
    async fn test_extract() {
        let path = Path::new("E:\\test\\markdown");

        let pool = MySqlPoolOptions::new()
            .connect("mysql://username:password@address/Blog")
            .await
            .expect("failed to connect database.");

        for entry in path.read_dir().expect("read_dir call failed") {
            if let Ok(entry) = entry {
                let _ = Extractor::extract(&entry, &pool);
            }
        }
    }

    #[tokio::test]
    async fn insert_db() {
        info!(">>>>>>>>>>>>>>>>>insert article into db");
        //let mysql_conn = MySqlPool::connect("mysql://username:password@address/Blog").await.expect("Failed to connect to database");

        let mysql_conn = MySqlPoolOptions::new()
            .connect("mysql://username:password@address/Blog")
            .await
            .expect("failed to connect database.");

        let _ = sqlx::query(
                    r#"INSERT INTO article (title, summary, path, createBy, createOn, modifyBy, modifyOn) VALUES ('UT', '鸿蒙系统', 'E:\\test.md',  'Owwww', now(), 'YYYY',now() )"#
                ).execute(&mysql_conn).await;
    }
}
